/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.events.aggr.concuser;

import java.io.Serializable;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Transient;
import org.apereo.portal.events.aggr.AggregationInterval;
import org.apereo.portal.events.aggr.BaseAggregationImpl;
import org.apereo.portal.events.aggr.DateDimension;
import org.apereo.portal.events.aggr.TimeDimension;
import org.apereo.portal.events.aggr.UniqueStrings;
import org.apereo.portal.events.aggr.groups.AggregatedGroupMapping;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.NaturalIdCache;

/** */
@Entity
@Table(name = "UP_CONCURRENT_USER_AGGR")
@Inheritance(strategy = InheritanceType.JOINED)
@SequenceGenerator(
        name = "UP_CONCURRENT_USER_AGGR_GEN",
        sequenceName = "UP_CONCURRENT_USER_AGGR_SEQ",
        allocationSize = 1000)
@TableGenerator(
        name = "UP_CONCURRENT_USER_AGGR_GEN",
        pkColumnValue = "UP_CONCURRENT_USER_AGGR_PROP",
        allocationSize = 1000)
@org.hibernate.annotations.Table(
        appliesTo = "UP_CONCURRENT_USER_AGGR",
        indexes = {
            @Index(
                    name = "IDX_UP_CONC_USER_AGGR_DTI",
                    columnNames = {"DATE_DIMENSION_ID", "TIME_DIMENSION_ID", "AGGR_INTERVAL"}),
            @Index(
                    name = "IDX_UP_CONC_USER_INTRVL",
                    columnNames = {"AGGR_INTERVAL"}),
            @Index(
                    name = "IDX_UP_CONC_USER_GRP",
                    columnNames = {"AGGR_GROUP_ID"})
        })
@NaturalIdCache(
        region = "org.apereo.portal.events.aggr.concuser.ConcurrentUserAggregationImpl-NaturalId")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public final class ConcurrentUserAggregationImpl
        extends BaseAggregationImpl<
                ConcurrentUserAggregationKey, ConcurrentUserAggregationDiscriminator>
        implements ConcurrentUserAggregation, Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(generator = "UP_CONCURRENT_USER_AGGR_GEN")
    @Column(name = "ID")
    private final long id;

    @Column(name = "CONCURRENT_USERS", nullable = false)
    private int concurrentUsers;

    @OneToOne(
            cascade = {CascadeType.ALL},
            orphanRemoval = true)
    @JoinColumn(name = "UNIQUE_STRINGS_ID")
    @Fetch(FetchMode.JOIN)
    private UniqueStrings uniqueStrings;

    @Transient private ConcurrentUserAggregationKey aggregationKey;
    @Transient private ConcurrentUserAggregationDiscriminator aggregationDiscriminator;

    @SuppressWarnings("unused")
    private ConcurrentUserAggregationImpl() {
        super();
        this.id = -1;
    }

    ConcurrentUserAggregationImpl(
            TimeDimension timeDimension,
            DateDimension dateDimension,
            AggregationInterval interval,
            AggregatedGroupMapping aggregatedGroup) {
        super(timeDimension, dateDimension, interval, aggregatedGroup);
        this.id = -1;
    }

    @Override
    public long getId() {
        return this.id;
    }

    @Override
    public int getConcurrentUsers() {
        return this.concurrentUsers;
    }

    @Override
    public ConcurrentUserAggregationKey getAggregationKey() {
        ConcurrentUserAggregationKey key = this.aggregationKey;
        if (key == null) {
            key = new ConcurrentUserAggregationKeyImpl(this);
            this.aggregationKey = key;
        }
        return key;
    }

    @Override
    public ConcurrentUserAggregationDiscriminator getAggregationDiscriminator() {
        ConcurrentUserAggregationDiscriminator discriminator = this.aggregationDiscriminator;
        if (discriminator == null) {
            discriminator = new ConcurrentUserAggregationDiscriminatorImpl(this);
            this.aggregationDiscriminator = discriminator;
        }
        return discriminator;
    }

    @Override
    protected boolean isComplete() {
        return this.concurrentUsers > 0 && this.uniqueStrings == null;
    }

    @Override
    protected void completeInterval() {
        this.uniqueStrings = null;
    }

    void countSession(String eventSessionId) {
        if (isComplete()) {
            this.getLogger()
                    .warn(
                            "{} is already closed, the event session {} will be ignored on: {}",
                            this.getClass().getSimpleName(),
                            eventSessionId,
                            this);
            return;
        }

        if (this.uniqueStrings == null) {
            this.uniqueStrings = new UniqueStrings();
        }

        if (this.uniqueStrings.add(eventSessionId)) {
            this.concurrentUsers++;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof ConcurrentUserAggregation)) return false;
        return super.equals(obj);
    }

    @Override
    public String toString() {
        return "ConcurrentUserAggregationImpl [id="
                + id
                + ", dateDimension="
                + getDateDimension()
                + ", timeDimension="
                + getTimeDimension()
                + ", interval="
                + getInterval()
                + ", aggregatedGroup="
                + getAggregatedGroup()
                + ", duration="
                + getDuration()
                + ", concurrentUsers="
                + concurrentUsers
                + "]";
    }
}
